# RPC(大点数脉冲压缩算法) ## 1. 概述 本文将以基于 FT04-DSP 的 rpc 算法的移植为例,介绍 AI+DSP 应用开发的开发流程。 算法简介: 大点数脉冲压缩(Pulse Compression)是雷达信号处理中的关键技术,该算法通过匹配滤波将宽脉冲信号压缩成窄脉冲,能够提高雷达的距离分辨率和检测能力,广泛用于气象雷达、合成孔径雷达、医学成像、地震勘探等需要处理超大点数信号的领域。 ## 2. FT04 c实现 主要代码如下 ``` void main(void) { int ID = getcoreid(); int iCntI, iCntJ; /**循环计数*/ int mask = 15; // 核掩码,低四位有效,0表示核0执行,3表示核0、核1执行,mask表示四个核执行 int iHeaderCPLX = 2048 / 8; /**回波协议*/ int iReadLenPerCore, iReadStPerCore, iWriteLenPerCore, iWriteStPerCore; int iFillZeroLen; int iPingPongFlag = 0; int iChannelNum = 5; /**总波束个数,也就是通道个数*/ int iImageValidRLen = 43456; /**距离向有效点数*/ int iRSampleLen = 43456; /**距离向采样长度*/ int iASampleLen = 17; /**方位向收数点数*/ int iRpcLength = 65536; /**脉压结果长度*/ int iTpADNum; /**脉冲宽度Tp内的采样点数*/ float fFs = 25; /**采样率MHz*/ float fTp = 210; /**脉冲宽度us*/ float fKr = 20.2775 / 210; /**调频斜率*/ float fLosVel = 1.485044537103641e+03; /**平台和目标沿雷达视线的速度分量,单位m/s*/ float fPRT = 2.38092e+03; /**发射脉冲重复间隔,单位us*/ float fDr = 5.99584916; /**距离单元宽度*/ float fTemp0; float r, theta; iTpADNum = (int)(fFs * fTp + 0.5); /**脉冲宽度Tp内的采样点数*/ iReadLenPerCore = iRSampleLen >> 2; iReadStPerCore = iReadLenPerCore * ID; iWriteLenPerCore = iImageValidRLen >> 2; iWriteStPerCore = iWriteLenPerCore * ID; iFillZeroLen = iRpcLength - iRSampleLen; CPLX *pcDDRfftTwid = (CPLX *)0x140000000; /**旋转因子 需要多少地址空间?*/ CPLX *pcDDRifftTwid = (CPLX *)0x180000000; /**旋转因子 需要多少地址空间?*/ float *pfGSMAngle = (float *)0x70320000; CPLX *pcGSMData1 = (CPLX *)(0x70330000); CPLX *pcGSMData2 = (CPLX *)(0x703b0000); CPLX *pcGSMfftBuf = (CPLX *)(0x70200000); /**算子缓存地址,所需空间为数据量的2倍,计算后释放*/ CPLX *pcGSMBuf = (CPLX *)(0x703a0000); /**缓存地址*/ CPLX *pcGSMRefBuf = (CPLX *)(0x70760000); /**运动补偿系数(方位线)缓存地址*/ CPLX *pcGSMPcRef = (CPLX *)(0x70430000); /**参考函数*/ CPLX *pcGSMRcRef = (CPLX *)(0x704b0000); /**运动补偿系数*/ CPLX *pcGSMRef = (CPLX *)(0x70730000); /**运动补偿系数*/ CPLX *pcGSMDataIn[2]; CPLX *pcGSMDataOut[2]; CPLX *pcDDRDataIn = (CPLX *)(0x80000000); /**原始回波存储地址*/ CPLX *pcDDRDataOut = (CPLX *)(0x100000000); /**结果存储地址*/ int *piGSMBuf = (int *)(0x70780000); pcGSMDataIn[0] = (CPLX *)(0x70530000); pcGSMDataIn[1] = (CPLX *)(0x705b0000); pcGSMDataOut[0] = (CPLX *)(0x70630000); pcGSMDataOut[1] = (CPLX *)(0x706b0000); unsigned long long cnt, cnt1, Use_Time, cnt2, cnt3, Use_Time1, cnt4, cnt5, Use_Time2, cnt6, cnt7, cnt8, Use_Time3, Use_Time4, Use_Time5, Use_Time6, Use_Time7; setTimerPeriod(0xffffffff); // 配置定时器周期0xffffffff configTimer(0x80); // 持续性计数,定时器当计数等于周期后一个时钟周期,计数重置为0再重新计数 asm("smfence\t"); timerStart(0); asm("smfence\t"); sys_bar(0, 4); cnt = getTimerCount(); int i; float *p; /**旋转因子初始化*/ gk_getw_core((CPLX *)pcDDRfftTwid, iRpcLength, 0, mask); sys_bar(0, 4); gk_getw_core((CPLX *)pcDDRifftTwid, iRpcLength, 1, mask); sys_bar(0, 4); /**计算脉压系数*/ get_angle_f_core(fFs, fKr, iTpADNum, pfGSMAngle, mask); sys_bar(0, 4); gk_cos_sin_f_core(pfGSMAngle, iTpADNum, pcGSMData1, mask); sys_bar(0, 4); gk_cvmemfill_f_core(0, 0, iRpcLength - iTpADNum, pcGSMData1 + iTpADNum, mask); sys_bar(1, 4); gk_getw_core((CPLX *)pcDDRfftTwid, iRpcLength, 0, mask); sys_bar(0, 4); gk_FFT_core((CPLX *)pcGSMData1, (CPLX *)pcDDRfftTwid, (CPLX *)pcGSMData2, iRpcLength, (CPLX *)pcGSMfftBuf, mask); /**FFT*/ sys_bar(12, 4); gk_bitrev_core((float *)pcGSMData2, iRpcLength, (float *)pcGSMPcRef, (float *)pcGSMfftBuf, mask); /**反序 输出为pcGSMRef*/ sys_bar(3, 4); gk_cvconj_f_core((float *)pcGSMPcRef, iRpcLength, (float *)pcGSMPcRef, mask); sys_bar(4, 4); gk_cvmemfill_f_core(1, 0, iRpcLength, pcGSMRcRef, mask); sys_bar(0, 4); /**运动补偿初始化*/ gk_cvmemfill_f_core(0, 1, iASampleLen, pcGSMRefBuf, mask); sys_bar(0, 4); fTemp0 = -fLosVel * fPRT * 1e-6 / fDr / iRpcLength; /**(velLos*prt*(noPulse-1)+rangeComp/2)/widthRange/numFFT*/ gk_tmuls_f_4core(-(iASampleLen - 1) / 2.0, fTemp0, iASampleLen, (float *)pcGSMBuf); /**rcCoef=exp(1j * 2 * pi * (-losVel*pri*((0:(prNum-1))-(prNum-1)/2)/disWidth)/range_fftNum);*/ sys_bar(0, 4); gk_vej_f_4core((float *)pcGSMBuf, pcGSMBuf, iASampleLen, pcGSMRefBuf); sys_bar(0, 4); /**这一块因为没有直接求int型向量的函数,所以用了两步*/ gk_tmuls_f_4core(-iRpcLength / 2, 1, iRpcLength, pcGSMRcRef); /**生成距离向向量*/ sys_bar(0, 4); gk_vmuls_i_core(pcGSMRcRef, 1, iRpcLength, piGSMBuf, 15); /**需要int型(做转换)*/ sys_bar(0, 4); /**读取单条脉冲单个通道的数据*/ pcDDRDataIn = pcDDRDataIn + iHeaderCPLX; DMA_Config(6, pcDDRDataIn + iReadStPerCore, pcGSMDataIn[iPingPongFlag] + iReadStPerCore, 0, 0, iReadLenPerCore, 8); DMA_start(0x40); DMA_check(0x40); sys_bar(0, 4); pcDDRDataIn = pcDDRDataIn + iRpcLength; // 源数据通道间隔为65536 cnt1 = getTimerCount(); Use_Time = (cnt1 - cnt) * 20; for (iCntJ = 0; iCntJ < iASampleLen; iCntJ++) /***/ { Use_Time1 = 0; Use_Time2 = 0; sys_bar(0, 4); cnt2 = getTimerCount(); r = sqrt(pcGSMRefBuf[iCntJ].re * pcGSMRefBuf[iCntJ].re + pcGSMRefBuf[iCntJ].im * pcGSMRefBuf[iCntJ].im); theta = atan(pcGSMRefBuf[iCntJ].im / pcGSMRefBuf[iCntJ].re); gk_cvpown_f_core(r, theta, iRpcLength, piGSMBuf, pcGSMData2, mask); sys_bar(0, 4); DMA_Config(0, pcGSMData2 + iRpcLength / 8 * ID, pcGSMRcRef + iRpcLength / 2 + iRpcLength / 8 * ID, 0, 0, 0x8000, 2); /**做一下fftshift*/ DMA_Config(1, pcGSMData2 + iRpcLength / 2 + iRpcLength / 8 * ID, pcGSMRcRef + iRpcLength / 8 * ID, 0, 0, 0x8000, 2); DMA_start(3); DMA_check(3); sys_bar(0, 4); cnt3 = getTimerCount(); Use_Time1 = (cnt3 - cnt2) * 20 + Use_Time1; for (iCntI = 0; iCntI < iChannelNum; iCntI++) /***/ { if (iCntI == (iChannelNum - 1)) { pcDDRDataIn = pcDDRDataIn + iHeaderCPLX; } sys_bar(0, 4); iPingPongFlag = ((iCntJ * iChannelNum) + iCntI + 1) % 2; /**读取单条脉冲单个通道的数据*/ DMA_Config(6, pcDDRDataIn + iReadStPerCore, pcGSMDataIn[iPingPongFlag] + iReadStPerCore, 0, 0, iReadLenPerCore, 8); DMA_start(0x40); iPingPongFlag = ((iCntJ * iChannelNum) + iCntI) % 2; gk_cvmemfill_f_core(0, 0, iFillZeroLen, pcGSMDataIn[iPingPongFlag] + iRSampleLen, mask); sys_bar(0, 4); gk_FFT_core((CPLX *)pcGSMDataIn[iPingPongFlag], (CPLX *)pcDDRfftTwid, (CPLX *)pcGSMData2, iRpcLength, (CPLX *)pcGSMfftBuf, mask); /**FFT*/ sys_bar(0, 4); gk_bitrev_core((float *)pcGSMData2, iRpcLength, (float *)pcGSMData1, (float *)pcGSMfftBuf, mask); /**反序 输出为pcGSMRef*/ sys_bar(0, 4); gk_cvmulcv_f_core(pcGSMData1, pcGSMRcRef, iRpcLength, pcGSMData1, mask); /**乘运动补偿*/ sys_bar(0, 4); gk_cvmulcv_f_core(pcGSMData1, pcGSMPcRef, iRpcLength, pcGSMData1, mask); /**乘脉压系数*/ sys_bar(0, 4); gk_IFFT_core((CPLX *)pcGSMData1, (CPLX *)pcDDRfftTwid, (CPLX *)pcGSMData2, iRpcLength, (CPLX *)pcGSMfftBuf, mask); /**FFT*/ sys_bar(0, 4); gk_bitrev_core((float *)pcGSMData2, iRpcLength, (float *)pcGSMDataOut[iPingPongFlag], (float *)pcGSMfftBuf, mask); /**反序 输出为pcGSMRef*/ sys_bar(0, 4); /**存储单条脉冲单个通道的数据*/ if (((iCntJ * iChannelNum) + iCntI) > 0) { DMA_check(0x80); } DMA_Config(7, pcGSMDataOut[iPingPongFlag] + iWriteStPerCore, pcDDRDataOut + iWriteStPerCore, 0, 0, iWriteLenPerCore, 8); DMA_start(0x80); sys_bar(0, 4); pcDDRDataIn = pcDDRDataIn + iRpcLength; // 源数据通道间隔为65536 pcDDRDataOut = pcDDRDataOut + iImageValidRLen; DMA_check(0x40); cnt4 = getTimerCount(); Use_Time2 = (cnt4 - cnt3) * 20 + Use_Time2; } } DMA_check(0x80); if (ID == 0) { printf("ok\n\n"); } } ``` 本代码用 FT-IDE 编译,能在ft04 板卡上加载、运行。 部分接口介绍: - `sys_bar` 为栅栏接口,用于多核同步:只有当所有核都完成当前计算任务后,才会开启下一个计算任务。 - `getTimerCount` 为时钟接口,它返回当前时钟的节拍数:用于性能统计。 - `DMA_Config` 为DMA传输接口,用于提升数据的搬移的速度。 - `gk_` 开头的函数为具体的 dsp 算子,算子的功能可通过查阅相关文档、头文件、代码可知,本文就不逐一介绍。 ## 3. 开发流程 ### 3.1 数据导入 c实现中,数据读取是通过IDE导入的。在python中使用 `open` 函数打开并读取原始的二进制数据文件,并把其转成`complex64`类型的复数tensor。 示例代码: ```python with open("data_43456_17_rpcdata_chnum5_RPCDataIn.bin", 'rb') as f: raw_data = f.read() echo = np.frombuffer(raw_data, dtype=np.complex64) echo = Tensor(echo) ``` ### 3.2 常量处理 移植时的主要工作是:将 c 算子的计算逻辑转换为 MindSpore Signal+ 的 API 调用。 整体分析可知,从数据导入到内层循环的fft之前的步骤,主要是模型常量的生成,另外乘法算子 ``` gk_cvmulcv_f_core(pcGSMData1, pcGSMRcRef, iRpcLength, pcGSMData1, mask); /**乘运动补偿*/ sys_bar(0, 4); gk_cvmulcv_f_core(pcGSMData1, pcGSMPcRef, iRpcLength, pcGSMData1, mask); /**乘脉压系数*/ ``` 中的 `pcGSMRcRef`、`pcGSMPcRef` 均为长度为 65536 的一维数据,其生成只依赖于给定的参数,不依赖于原始输入,为模型中的常量。 为避免额外的开销,可将其相乘后放在`__init__`中。 示例代码: ```python class rpc(nn.Cell): def __init__(self): super(rpc, self).__init__() fFs = 25 fTp = 210 fKr = 20.2775 / 210 iTpADNum = (int)(fFs * fTp + 0.5) iRpcLength = 65536 ... ``` ### 3.3 核心计算 循环部分是算法的核心,其主要流程是在循环中对一维的数据做fft、乘法和ifft运算。 可使用下列优化方法来提升性能: - 维度提升:因为框架中算子:fft、乘法和ifft均支持二维输入,所以把核心中的一维算子、数据改写成二维的,这样可节省框架的循环调用的开销。 ``` gk_FFT_core((CPLX *)pcGSMDataIn[iPingPongFlag], (CPLX *)pcDDRfftTwid, (CPLX *)pcGSMData2, iRpcLength, (CPLX *)pcGSMfftBuf, mask); /**FFT*/ ``` 中的 `pcGSMDataIn` 为长度为 65536 的一维输入数据,可把其当成一个二维输入 tensor 中的一行。 ``` gk_bitrev_core((float *)pcGSMData2, iRpcLength, (float *)pcGSMDataOut[iPingPongFlag], (float *)pcGSMfftBuf, mask); /**反序 输出为pcGSMRef*/ ``` 中的 `pcGSMDataOut` 为长度为 65536 的一维输出数据,可把其当成一个二维输出 tensor 中的一行。 - 空间分配:在运行前为框架算子缓存区分配空间,并将存储类型设置为 `smc`,这样能提升算子的访存速度,对于此应用,使用`smc`前后运行时间分别为:88ms、70ms,性能提升了 18 ms。 示例代码: ```python def construct(self, t): t = t.reshape(self.iASampleLen, t.size//self.iASampleLen) t = self.slice(t, [0, self.iHeaderCPLX], [self.iASampleLen, self.iRpcLength*self.iChannelNum]) t = t.reshape(self.line, self.iRpcLength) t = self.slice(t, [0,0], [self.line, self.iRSampleLen]) t = ops.cat((t, self.zero), 1) t = self.fft(t) t = self.mul(t, self.pcGSMRcRef) t = self.ifft(t) t = self.slice(t, [0,0], [self.line, self.iRSampleLen]) t = t.reshape(t.size) return t ``` ### python完整代码示例: ```python import numpy as np import mindspore as ms import mindradar as mr from mindspore import Tensor, ops, nn import data_compare as compare class rpc(nn.Cell): def __init__(self): super(rpc, self).__init__() fFs = 25 fTp = 210 fKr = 20.2775 / 210 iTpADNum = (int)(fFs * fTp + 0.5) iRpcLength = 65536 iRSampleLen = 43456 iASampleLen = 17 iChannelNum = 5 f_n = iRpcLength - iTpADNum pfGSMAngle = np.zeros(iTpADNum, dtype=np.float32) for i in range(iTpADNum): tr = (i - iTpADNum / 2.0) / fFs pfGSMAngle[i] = np.pi * fKr * tr * tr pcGSMData1 = pfGSMAngle.astype(np.complex64) pcGSMData1.real = np.cos(pfGSMAngle) pcGSMData1.imag = np.sin(pfGSMAngle) zero = np.zeros(f_n, dtype=np.complex64) pcGSMData1 = np.append(pcGSMData1, zero, axis=0) pcGSMPcRef = np.fft.fft(pcGSMData1).astype(np.complex64) pcGSMPcRef = np.conj(pcGSMPcRef) tmuls_0 = -(iASampleLen - 1) / 2.0 fLosVel = 1.485044537103641e+03 fPRT = 2.38092e+03 fDr = 5.99584916 fTemp0 = -fLosVel * fPRT * 1e-6 / fDr / iRpcLength pcGSMBuf = np.zeros(iASampleLen, dtype=np.float32) for i in range(iASampleLen): pcGSMBuf[i] = (tmuls_0 + i) * fTemp0 pcGSMRefBuf = pcGSMBuf.astype(np.complex64) pcGSMRefBuf.real = np.cos(pcGSMBuf * 2 * np.pi) pcGSMRefBuf.imag = np.sin(pcGSMBuf * 2 * np.pi) piGSMBuf = np.linspace(-iRpcLength / 2, iRpcLength / 2 - 1, iRpcLength).astype(np.int32) line = iASampleLen * iChannelNum pcGSMData2 = np.zeros(line * iRpcLength, dtype=np.complex64).reshape(line, iRpcLength) for j in range(iASampleLen): for i in range(iChannelNum): pcGSMData2[j * iChannelNum + i] = np.power(pcGSMRefBuf[j], piGSMBuf) pcGSMRcRef = np.fft.fftshift(pcGSMData2, 1) pcGSMRcRef = pcGSMRcRef * pcGSMPcRef self.pcGSMRcRef = Tensor(pcGSMRcRef) self.iASampleLen = iASampleLen zero = np.zeros(line*(iRpcLength-iRSampleLen), dtype=np.complex64).reshape(line, iRpcLength-iRSampleLen) self.zero = Tensor(zero) self.iRpcLength = iRpcLength self.iRSampleLen = iRSampleLen self.iChannelNum = iChannelNum self.line = line self.iHeaderCPLX = 256 self.fft = mr.FFT() self.ifft = mr.IFFT() self.mul = ops.Mul() self.slice = ops.Slice() def construct(self, t): t = t.reshape(self.iASampleLen, t.size//self.iASampleLen) t = self.slice(t, [0, self.iHeaderCPLX], [self.iASampleLen, self.iRpcLength*self.iChannelNum]) t = t.reshape(self.line, self.iRpcLength) t = self.slice(t, [0,0], [self.line, self.iRSampleLen]) t = ops.cat((t, self.zero), 1) t = self.fft(t) t = self.mul(t, self.pcGSMRcRef) t = self.ifft(t) t = self.slice(t, [0,0], [self.line, self.iRSampleLen]) t = t.reshape(t.size) return t with open("data_43456_17_rpcdata_chnum5_RPCDataIn.bin", 'rb') as f: raw_data = f.read() echo = np.frombuffer(raw_data, dtype=np.complex64) echo = Tensor(echo) fun = rpc() out = fun(echo) ms.export(fun, echo, file_name="rpc", file_format="MINDIR") print('out', out.shape) print('out', out) out = out.asnumpy() compare.data_compare(out, "out_43456_5_17.bin") compare.data_compare(out, "rpc_model_out.bin") ``` ## 4. 转换模型 在上述代码中,我们已经使用了 `export` 方法导出了`rpc.mindir`模型。 要将该模型部署到 FT04 平台,需要将 `mindir` 格式转换成 mindspore lite 的 `ms` 格式。 现使用 MindSpore 的转换工具:`converter_lite` 进行转换。 转换命令如下: ``` ./converter_lite --fmk=MINDIR --modelFile=/mnt/hgfs/D/work/mind-radar/examples/rpc/rpc.mindir --outputFile=/mnt/hgfs/D/work/mind-radar/examples/rpc/rpc ``` 转换完成后,生成模型`rpc.ms` 后,可使用可视化工具:`netron` 打开模型,查看模型结构。 模型结构如图所示: